// Inspired by base2 and Prototype
(function() {
    var initializing = false;

    // The base JQClass implementation (does nothing)
    window.JQClass = function() {};

    // Collection of derived classes
    JQClass.classes = {};

    // Create a new JQClass that inherits from this class
    JQClass.extend = function extender(prop) {
        var base = this.prototype;

        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;

        // Copy the properties over onto the new prototype
        for (var name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] == 'function' &&
                typeof base[name] == 'function' ?
                (function(name, fn) {
                    return function() {
                        var __super = this._super;

                        // Add a new ._super() method that is the same method
                        // but on the super-class
                        this._super = function(args) {
                            return base[name].apply(this, args || []);
                        };

                        var ret = fn.apply(this, arguments);

                        // The method only need to be bound temporarily, so we
                        // remove it when we're done executing
                        this._super = __super;

                        return ret;
                    };
                })(name, prop[name]) :
                prop[name];
        }

        // The dummy class constructor
        function JQClass() {
            // All construction is actually done in the init method
            if (!initializing && this._init) {
                this._init.apply(this, arguments);
            }
        }

        // Populate our constructed prototype object
        JQClass.prototype = prototype;

        // Enforce the constructor to be what we expect
        JQClass.prototype.constructor = JQClass;

        // And make this class extendable
        JQClass.extend = extender;

        return JQClass;
    };
})();

(function($) { // Ensure $, encapsulate

    /** Abstract base class for collection plugins v1.0.1.
    	Written by Keith Wood (kbwood{at}iinet.com.au) December 2013.
    	Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license.
    	@module $.JQPlugin
    	@abstract */
    JQClass.classes.JQPlugin = JQClass.extend({

        /** Name to identify this plugin.
        	@example name: 'tabs' */
        name: 'plugin',

        /** Default options for instances of this plugin (default: {}).
			@example defaultOptions: {
 	selectedClass: 'selected',
 	triggers: 'click'
 } */
        defaultOptions: {},

        /** Options dependent on the locale.
			Indexed by language and (optional) country code, with '' denoting the default language (English/US).
			@example regionalOptions: {
	'': {
		greeting: 'Hi'
	}
 } */
        regionalOptions: {},

        /** Names of getter methods - those that can't be chained (default: []).
        	@example _getters: ['activeTab'] */
        _getters: [],

        /** Retrieve a marker class for affected elements.
        	@private
        	@return {string} The marker class. */
        _getMarker: function() {
            return 'is-' + this.name;
        },

        /** Initialise the plugin.
        	Create the jQuery bridge - plugin name <code>xyz</code>
        	produces <code>$.xyz</code> and <code>$.fn.xyz</code>. */
        _init: function() {
            // Apply default localisations
            $.extend(this.defaultOptions, (this.regionalOptions && this.regionalOptions['']) || {});
            // Camel-case the name
            var jqName = camelCase(this.name);
            // Expose jQuery singleton manager
            $[jqName] = this;
            // Expose jQuery collection plugin
            $.fn[jqName] = function(options) {
                var otherArgs = Array.prototype.slice.call(arguments, 1);
                if ($[jqName]._isNotChained(options, otherArgs)) {
                    return $[jqName][options].apply($[jqName], [this[0]].concat(otherArgs));
                }
                return this.each(function() {
                    if (typeof options === 'string') {
                        if (options[0] === '_' || !$[jqName][options]) {
                            throw 'Unknown method: ' + options;
                        }
                        $[jqName][options].apply($[jqName], [this].concat(otherArgs));
                    } else {
                        $[jqName]._attach(this, options);
                    }
                });
            };
        },

        /** Set default values for all subsequent instances.
        	@param options {object} The new default options.
        	@example $.plugin.setDefauls({name: value}) */
        setDefaults: function(options) {
            $.extend(this.defaultOptions, options || {});
        },

        /** Determine whether a method is a getter and doesn't permit chaining.
        	@private
        	@param name {string} The method name.
        	@param otherArgs {any[]} Any other arguments for the method.
        	@return {boolean} True if this method is a getter, false otherwise. */
        _isNotChained: function(name, otherArgs) {
            if (name === 'option' && (otherArgs.length === 0 ||
                    (otherArgs.length === 1 && typeof otherArgs[0] === 'string'))) {
                return true;
            }
            return $.inArray(name, this._getters) > -1;
        },

        /** Initialise an element. Called internally only.
        	Adds an instance object as data named for the plugin.
        	@param elem {Element} The element to enhance.
        	@param options {object} Overriding settings. */
        _attach: function(elem, options) {
            elem = $(elem);
            if (elem.hasClass(this._getMarker())) {
                return;
            }
            elem.addClass(this._getMarker());
            options = $.extend({}, this.defaultOptions, this._getMetadata(elem), options || {});
            var inst = $.extend({
                    name: this.name,
                    elem: elem,
                    options: options
                },
                this._instSettings(elem, options));
            elem.data(this.name, inst); // Save instance against element
            this._postAttach(elem, inst);
            this.option(elem, options);
        },

        /** Retrieve additional instance settings.
			Override this in a sub-class to provide extra settings.
			@param elem {jQuery} The current jQuery element.
			@param options {object} The instance options.
			@return {object} Any extra instance values.
			@example _instSettings: function(elem, options) {
 	return {nav: elem.find(options.navSelector)};
 } */
        _instSettings: function(elem, options) {
            return {};
        },

        /** Plugin specific post initialisation.
			Override this in a sub-class to perform extra activities.
			@param elem {jQuery} The current jQuery element.
			@param inst {object} The instance settings.
			@example _postAttach: function(elem, inst) {
 	elem.on('click.' + this.name, function() {
 		...
 	});
 } */
        _postAttach: function(elem, inst) {},

        /** Retrieve metadata configuration from the element.
        	Metadata is specified as an attribute:
        	<code>data-&lt;plugin name>="&lt;setting name>: '&lt;value>', ..."</code>.
        	Dates should be specified as strings in this format: 'new Date(y, m-1, d)'.
        	@private
        	@param elem {jQuery} The source element.
        	@return {object} The inline configuration or {}. */
        _getMetadata: function(elem) {
            try {
                var data = elem.data(this.name.toLowerCase()) || '';
                data = data.replace(/'/g, '"');
                data = data.replace(/([a-zA-Z0-9]+):/g, function(match, group, i) {
                    var count = data.substring(0, i).match(/"/g); // Handle embedded ':'
                    return (!count || count.length % 2 === 0 ? '"' + group + '":' : group + ':');
                });
                data = $.parseJSON('{' + data + '}');
                for (var name in data) { // Convert dates
                    var value = data[name];
                    if (typeof value === 'string' && value.match(/^new Date\((.*)\)$/)) {
                        data[name] = eval(value);
                    }
                }
                return data;
            } catch (e) {
                return {};
            }
        },

        /** Retrieve the instance data for element.
        	@param elem {Element} The source element.
        	@return {object} The instance data or {}. */
        _getInst: function(elem) {
            return $(elem).data(this.name) || {};
        },

        /** Retrieve or reconfigure the settings for a plugin.
			@param elem {Element} The source element.
			@param name {object|string} The collection of new option values or the name of a single option.
			@param [value] {any} The value for a single named option.
			@return {any|object} If retrieving a single value or all options.
			@example $(selector).plugin('option', 'name', value)
 $(selector).plugin('option', {name: value, ...})
 var value = $(selector).plugin('option', 'name')
 var options = $(selector).plugin('option') */
        option: function(elem, name, value) {
            elem = $(elem);
            var inst = elem.data(this.name);
            if (!name || (typeof name === 'string' && value == null)) {
                var options = (inst || {}).options;
                return (options && name ? options[name] : options);
            }
            if (!elem.hasClass(this._getMarker())) {
                return;
            }
            var options = name || {};
            if (typeof name === 'string') {
                options = {};
                options[name] = value;
            }
            this._optionsChanged(elem, inst, options);
            $.extend(inst.options, options);
        },

        /** Plugin specific options processing.
			Old value available in <code>inst.options[name]</code>, new value in <code>options[name]</code>.
			Override this in a sub-class to perform extra activities.
			@param elem {jQuery} The current jQuery element.
			@param inst {object} The instance settings.
			@param options {object} The new options.
			@example _optionsChanged: function(elem, inst, options) {
 	if (options.name != inst.options.name) {
 		elem.removeClass(inst.options.name).addClass(options.name);
 	}
 } */
        _optionsChanged: function(elem, inst, options) {},

        /** Remove all trace of the plugin.
        	Override <code>_preDestroy</code> for plugin-specific processing.
        	@param elem {Element} The source element.
        	@example $(selector).plugin('destroy') */
        destroy: function(elem) {
            elem = $(elem);
            if (!elem.hasClass(this._getMarker())) {
                return;
            }
            this._preDestroy(elem, this._getInst(elem));
            elem.removeData(this.name).removeClass(this._getMarker());
        },

        /** Plugin specific pre destruction.
			Override this in a sub-class to perform extra activities and undo everything that was
			done in the <code>_postAttach</code> or <code>_optionsChanged</code> functions.
			@param elem {jQuery} The current jQuery element.
			@param inst {object} The instance settings.
			@example _preDestroy: function(elem, inst) {
 	elem.off('.' + this.name);
 } */
        _preDestroy: function(elem, inst) {}
    });

    /** Convert names from hyphenated to camel-case.
    	@private
    	@param value {string} The original hyphenated name.
    	@return {string} The camel-case version. */
    function camelCase(name) {
        return name.replace(/-([a-z])/g, function(match, group) {
            return group.toUpperCase();
        });
    }

    /** Expose the plugin base.
    	@namespace "$.JQPlugin" */
    $.JQPlugin = {

        /** Create a new collection plugin.
			@memberof "$.JQPlugin"
			@param [superClass='JQPlugin'] {string} The name of the parent class to inherit from.
			@param overrides {object} The property/function overrides for the new class.
			@example $.JQPlugin.createPlugin({
 	name: 'tabs',
 	defaultOptions: {selectedClass: 'selected'},
 	_initSettings: function(elem, options) { return {...}; },
 	_postAttach: function(elem, inst) { ... }
 }); */
        createPlugin: function(superClass, overrides) {
            if (typeof superClass === 'object') {
                overrides = superClass;
                superClass = 'JQPlugin';
            }
            superClass = camelCase(superClass);
            var className = camelCase(overrides.name);
            JQClass.classes[className] = JQClass.classes[superClass].extend(overrides);
            new JQClass.classes[className]();
        }
    };

})(jQuery);